#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/cdev.h>
#include <linux/device.h>

//register phy address
#define CCM_CCGR1_BASE			(0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE  (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE  (0X020E02F4)
#define GPIO1_DR_BASE		    (0X0209C000)
#define GPIO1_GDIR_BASE		    (0X0209C004)

//虚拟地址指针
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

#define LEDOFF 0
#define LEDON  1

#define NEWCHRLED_NAME	"newchrled"
#define NEWCHRLED_CNT	1

//led 设备结构体
struct newchrled_dev 
{
	struct cdev cdev;		//字符设备
	dev_t	devid;			//设备号
	struct class *class;	//类，用于自动创建节点
	struct device *device;  //用于创建设备
	int major;				//主设备号
	int minor;				//次设备号
};

struct newchrled_dev newchrled;	//led 设备

static void led_switch(uint8_t sta)
{
	uint32_t val = 0;
	if(sta == LEDON)
	{
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);		//bit3清零 打开led
		writel(val,GPIO1_DR);		//设置为输出
	}
	else if(sta == LEDOFF)
	{
		val = readl(GPIO1_DR);
		val |= (1 << 3);		//bit3置位 关闭开led
		writel(val,GPIO1_DR);		//设置为输出
	}
}

static int newchrled_open(struct inode *inode, struct file *filp)
{
	//printk("led open!\r\n");
	filp->private_data = &newchrled;
	return 0;
}

static ssize_t newchrled_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
	unsigned int retvalue = 0;
	unsigned char databuf[1];
	/* 接收用户空间传递给内核的数据并且打印出来 */
	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0)
	{
		printk("kernel writefailed\r\n");
	}
	led_switch(databuf[0]);
	return 0;
}

static int newchrled_close(struct inode *inode,struct file *filp)
{
	struct newchrled_dev *dev = (struct newchrled_dev*)filp->private_data;

	
	//printk("led release!\r\n");
	return 0;
}

static const struct file_operations newchrled_fops = 
{
	.owner = THIS_MODULE,
	.write = newchrled_write,
	.open = newchrled_open,
	.release = newchrled_close,
};

//入口函数
static int __init newchrled_init(void)
{
	int ret = 0;
	unsigned int val_temp = 0;
	printk("newchrled_init\r\n");
	//1.初始化led
	IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);  //地址映射
	SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
	SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
	GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
	GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);

	//GPIO clock
	val_temp = readl(IMX6U_CCM_CCGR1);
	val_temp &= ~(3<<26); //clear bit 26 27
	val_temp |= 3 << 26; //set bit 26 27
	writel(val_temp,IMX6U_CCM_CCGR1);

	writel(0x5,SW_MUX_GPIO1_IO03);		//复用为GPIO功能
	writel(0x10B0,SW_PAD_GPIO1_IO03);	//设置电气属性

	val_temp = readl(GPIO1_GDIR);
	val_temp |= 1 << 3;
	writel(val_temp,GPIO1_GDIR);		//设置为输出

	val_temp = readl(GPIO1_DR);
	val_temp &= ~(1 << 3);		//bit3清零 打开led
	writel(val_temp,GPIO1_DR);		//设置为输出
	//2.注册字符设备
	if (newchrled.major)	//给定
	{
		newchrled.devid = MKDEV(newchrled.major,0);
		ret = register_chrdev_region(newchrled.devid,NEWCHRLED_CNT,NEWCHRLED_NAME);
	}
	else
	{
		ret = alloc_chrdev_region(&newchrled.devid,0,NEWCHRLED_CNT,NEWCHRLED_NAME);
		newchrled.major = MAJOR(newchrled.devid);
		newchrled.minor = MINOR(newchrled.devid);
	}
	if (ret < 0)
	{
		printk("newchrled chrdev_region err!\r\n");
	}
	printk("newchrled major:%d minor:%d\r\n",newchrled.major,newchrled.minor);
	newchrled.cdev.owner = THIS_MODULE;
	ret = cdev_init(&newchrled.cdev,&newchrled_fops);
	if (ret < 0)
	{
		goto fail_cdevinit;
	}
	
	ret = cdev_add(&newchrled.cdev,newchrled.devid,NEWCHRLED_CNT);

	//自动创建设备节点
	//1.创建一个class的类
	newchrled.class =  class_create(newchrled.cdev.owner,NEWCHRLED_NAME);
	if(IS_ERR(newchrled.class))
		return PTR_ERR(newchrled.class);
	//2.创建设备
	newchrled.device = device_create(newchrled.class,NULL,newchrled.devid,NULL,NEWCHRLED_NAME);
	if(IS_ERR(newchrled.device))
		return PTR_ERR(newchrled.device);
	return ret;
fail_cdevinit:
	return -1;
}

//出口函数
static void __exit newchrled_exit(void)
{
	unsigned int val_temp = 0;

	val_temp = readl(GPIO1_DR);
	val_temp |= (1 << 3);		//bit3置位 关闭开led
	writel(val_temp,GPIO1_DR);		//设置为输出

	iounmap(IMX6U_CCM_CCGR1);	//取消地址映射
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	printk("newchrled_exit\r\n");
	//1.删除字符设备
	cdev_del(&newchrled.cdev);
	//2.注销设备号
	unregister_chrdev_region(newchrled.devid,NEWCHRLED_CNT);
	//3.摧毁设备
	device_destroy(newchrled.class,newchrled.devid);
	//4.摧毁类
	class_destroy(newchrled.class);
}

//注册和卸载驱动
module_init(newchrled_init);
module_exit(newchrled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Eason");